//watcher监听器

import {
  pushTarget,
  popTarget
} from './dep';

import {
  getValue
} from '../utils';


//给watcher添加唯一id
let id = 0;

class Watcher {
  /**
   * 
   * @param {*} vm 实例
   * @param {*} expOrFn 表达式或函数
   * @param {*} cb 回调函数 选填 默认为()=>{}
   * @param {*} opts 配置项 选填 默认为{}
   */
  constructor(vm, expOrFn, cb = () => {}, opts = {}) {
    //挂载到实例
    this.vm = vm;
    this.expOrFn = expOrFn;
    this.cb = cb;
    this.opts = opts;
    //每次实例化watcher会有一个id,来区分不同的watcher
    this.id = id++;

    //存放dep
    this.deps = [];

    //避免重复
    this.depsId = new Set();

    //根据传入的配置项{user: true}而做的判断
    if (opts.user) {
      this.user = true;
    }

    //是函数
    if (typeof expOrFn === 'function') {
      // 这里 expOrFn 是 用户写好的函数
      this.getter = expOrFn;
    } else {
      //是表达式
      // this.getter 会拿到 message/number 属性值:一个事件处理函数
      this.getter = function () {
        //这里是 expOrFn 是字符串表达式 message/number 即watch对象里的属性名
        return getValue(expOrFn, this.vm);
      }

      // console.log(this.getter);
      //f(){...}

    }

    //实例化时默认执行getter方法
    //如果是传过来的函数会执行函数程序
    //this.value会在实例化的时候初始化
    //对应watch对象里面的某属性对应函数的oldValue形参
    /**
     * watch{
     *   message: function(newValue, oldValue){...}
     * }
     */
    this.value = this.get();

  }

  get() {
    //入栈 
    //this -> 实例化的watcher
    pushTarget(this);

    //更新组件和渲染页面
    let value = this.getter();

    //出栈
    popTarget();

    //执行后会拿到 message/number 属性值:一个事件处理函数 返回的结果 
    // console.log(value);
    return value;

  }

  //保存所有依赖的subs底下都有一个update方法
  update() {
    //watcher队列
    //this => watcher实例
    queueWatcher(this);
  }

  //运行get方法
  run() {
    //重新更新组件和渲染页面
    let value = this.get();

    //这里value是新的值
    //在watch里 当老的值不等于新的值
    if (this.value !== value) {
      this.cb(value, this.value);
    }
  }



  //让watcher和dep互相依赖
  //根据Id避免重复
  addDep(dep) {
    let id = dep.id;
    // console.log(id); 0

    //没有存过dep时存放dep
    if (!this.depsId.has(id)) {
      this.depsId.add(id);

      /**
       * console.log(this.depsId);
       * Set {
       *   0: 0
       * }
       */

      //dep存入到了watcher中
      this.deps.push(dep);
      // console.log(this.deps);
      //[Dep]

      //订阅
      dep.addSub(this);
      // console.log(dep);
      //Dep {id: 0, subs: [Watcher]}

      // console.log(this);
      // Watcher {deps: [Dep], depsId: Set(1) {0}, vm: Vue,, id: 0, , …}
    }
  }

}

let has = {};
let queue = [];

//watcher队列管理
function queueWatcher(watcher) {
  let id = watcher.id;

  //当没有id属性时才把watcher放入队列
  if (has[id] == null) {

    has[id] = true;
    // console.log(has);
    //{0: true}

    queue.push(watcher);

    //延迟清空队列
    $nextTick(flushQueue);
  }
}

//清空队列
//把更新保存起来，等更新完毕后让watcher依次执行
function flushQueue() {
  // console.log(queue);
  //[Watcher]

  queue.forEach(watcher => watcher.run());
  has = {};
  queue = [];
}

//存放回调函数队列
let callbacks = [];

//清空回调函数
function flushCallBacks() {
  //执行每一个回调函数
  callbacks.forEach(cb => cb());
}

//延迟
//异步刷新回调函数队列的方式
function $nextTick(cb) {
  //执行异步函数

  //开放用户编写的回调函数
  //保证页面更新完成后，再去访问DOM节点

  //存入回调函数队列
  callbacks.push(cb);

  let timerFunction = () => {
    flushCallBacks();
  }

  //让flushCallBacks异步执行的几种方法
  //看浏览器是否兼容异步方法


  //方法一：Promise
  if (Promise) {
    return Promise.resolve().then(timerFunction);
  }

  //方法二：html5 API
  if (MutationObserver) {
    let observe = new MutationObserver(timerFunction);
    //假如有文本节点
    let textNode = document.createTextNode(10);
    //监听textNode变化
    //characterData变化证明文本节点发生变化
    observe.observe(textNode, {
      characterData: true
    });
    textNode.textContent = 20;
    return;
  }

  //方法三：类似setTimeout 性能优于setTimeout 老版本浏览器不兼容
  if (setImmediate) {
    return setImmediate(timerFunction);
  }

  //方法四：
  setTimeout(timerFunction, 0);


}

export default Watcher;